#include "stdafx.h"
#include "CppUnitTest.h"

using namespace Microsoft::VisualStudio::CppUnitTestFramework;

#include <vex/core/as_intrusive.h>
#include <vex/iterable/make_any_range.h>
#include <vex/iterable/any_range.h>
#include <vex/functional/implementation/default_value.h>
#include <vex/functional/implementation/default_value_reducer.h>
#include <vex/functional/contract/multicast_delegate.h>
#include <vex/functional/make_raw_multicast_delegate.h>
#include <vex/functional/concept/reducer_concept.h>
#include <vex/functional/make_any_delegate.h>

namespace tests { namespace tests_functional_mc_create_multicast_delegate {		

    namespace vf = vex::functional;
    namespace vi = vex::iterable;

    TEST_CLASS(test_class) 
    {
        BEGIN_TEST_CLASS_ATTRIBUTE()
            TEST_CLASS_ATTRIBUTE(L"Module", L"tests.functional.mc.create.multicast_delegate")
        END_TEST_CLASS_ATTRIBUTE();
    public:
        static const int c_42 = 42;
        static int s_42;

        static int const & returns_const_ref()
        {
            return c_42;
        }

        static int const * returns_const_ptr()
        {
            return &c_42;
        }

        static int& returns_ref()
        {
            return s_42;
        }

        static int* returns_ptr()
        {
            return &s_42;
        }

        template<class T> static T* returns_null_ptr()
        {
            return nullptr;
        }

        template<class T> static T returns_temporary()
        {
            return T();
        }

        template<class T> static T&& returns_rvalue()
        {
            return T();
        }

        VEX_TESTS_TRACKED_MEM_CHECK();

        TEST_METHOD_INITIALIZE(set_up)
        {
            VEX_TESTS_TRACKED_MEM_CHECK_START();
        }

        TEST_METHOD_CLEANUP(tear_down)
        {
            VEX_TESTS_TRACKED_MEM_CHECK_FINISH();
        }

        TEST_METHOD(should_create_expected_type_if_return_is_void_default_reducer)
        {
            
            namespace vfi = vf::implementation;

            { // default_value_reducer void return type
                typedef void (signature_t)();
                typedef vf::result<signature_t>::type result_t;
                typedef vfi::multicast_delegate_default_impl<signature_t>* exp_t;

                typedef decltype(vf::make_raw_multicast_delegate<signature_t>()) act_t;

                Assert::IsTrue(std::is_same<exp_t, act_t>::value, wlog_message() 
                    << "expected: " << std::endl 
                    << typeid(exp_t).name() << std::endl
                    << "got: " << std::endl
                    << typeid(act_t).name() | as_string);
            }
        }

        TEST_METHOD(should_create_expected_type_if_return_is_non_void_default_reducer)
        {
            
            namespace vfi = vf::implementation;

            { // default_value_reducer non-void, non-reference return type
                typedef int (signature_t)();
                typedef vf::result<signature_t>::type result_t;
                typedef vfi::multicast_delegate_default_impl<signature_t>* exp_t;

                typedef decltype(vf::make_raw_multicast_delegate<signature_t>(vf::default_reducer(42))) act_t;
                Assert::IsTrue(std::is_same<exp_t, act_t>::value, wlog_message() 
                    << "expected: " << std::endl 
                    << typeid(exp_t).name() << std::endl
                    << "got: " << std::endl
                    << typeid(act_t).name() | as_string);
            }
        }

        TEST_METHOD(should_create_expected_type_if_return_is_ref_const_default_reducer)
        {
            
            namespace vfi = vf::implementation;

            { // default_value_reducer non-void, reference to const return type  
                typedef int const & (signature_t)();
                typedef vf::result<signature_t>::type result_t;
                typedef vfi::multicast_delegate_default_impl<signature_t>* exp_t;

                typedef decltype(vf::make_raw_multicast_delegate<signature_t>(vf::default_reducer(c_42))) act_t;
                Assert::IsTrue(std::is_same<exp_t, act_t>::value, wlog_message() 
                    << "expected: " << std::endl 
                    << typeid(exp_t).name() << std::endl
                    << "got: " << std::endl
                    << typeid(act_t).name() | as_string);
            }
        }

        TEST_METHOD(should_create_expected_type_if_return_is_ref_default_reducer)
        {
            
            namespace vfi = vf::implementation;

            { // default_value_reducer non-void, reference return type   
                typedef int & (signature_t)();
                typedef vf::result<signature_t>::type result_t;
                typedef vfi::multicast_delegate_default_impl<signature_t>* exp_t;

                typedef decltype(vf::make_raw_multicast_delegate<signature_t>(vf::default_reducer(s_42))) act_t;
                Assert::IsTrue(std::is_same<exp_t, act_t>::value, wlog_message() 
                    << "expected: " << std::endl 
                    << typeid(exp_t).name() << std::endl
                    << "got: " << std::endl
                    << typeid(act_t).name() | as_string);
            }
        }

        TEST_METHOD(should_create_expected_type_if_return_is_non_void_custom_reducer)
        {
            
            namespace vfi = vf::implementation;

            { // custom reducer:
                typedef int (signature_t)();
                typedef vi::contract::range<vi::one_pass, int>* reducer_result_t;
                auto t_reducer = [](vf::contract::invoker_range<int>*) -> reducer_result_t { 
                    return reducer_result_t(nullptr); 
                };
                typedef std::remove_reference<decltype(t_reducer)>::type reducer_t;
                typedef vfi::multicast_delegate_default_impl<signature_t, reducer_t>* exp_t;

                (void)t_reducer;

                Assert::IsTrue(std::is_same<
                    reducer_result_t, 
                    decltype(t_reducer(std::declval<vf::contract::invoker_range<int>*>()))
                >::value);

                Assert::IsTrue(std::is_same<
                    reducer_result_t, 
                    std::result_of<
                    decltype(t_reducer)
                    (vf::contract::invoker_range<int>*)
                    >::type
                >::value);

                BOOST_CONCEPT_ASSERT((vf::concept::Reducer<reducer_t, signature_t>));

                typedef decltype(vf::make_raw_multicast_delegate<signature_t>(t_reducer)) act_t;
                Assert::IsTrue(std::is_same<exp_t, act_t>::value, wlog_message() 
                    << "expected: " << std::endl 
                    << typeid(exp_t).name() << std::endl
                    << "got: " << std::endl
                    << typeid(act_t).name() | as_string);
            }
        }

        TEST_METHOD(should_create_valid_object_if_return_is_void_default_reducer)
        {
            
            namespace vfi = vf::implementation;

            // default_value_reducer void return type
            typedef void (signature_t)();
            typedef vf::result<signature_t>::type result_t;
            typedef vfi::multicast_delegate_default_impl<signature_t>* exp_t;

            if (auto mc = vex::as_intrusive_from_out(vf::make_raw_multicast_delegate<signature_t>())) {
                typedef decltype(mc) act_ip_t;
                typedef act_ip_t::element_type* act_t;

                Assert::IsTrue(std::is_same<exp_t, act_t>::value, wlog_message() 
                    << "expected: " << std::endl
                    << typeid(exp_t).name() << std::endl
                    << "got: " << std::endl
                    << typeid(act_t).name() | as_string);
            } else {
                Assert::Fail(L"make_raw_multicast_delegate failed!");
            }
        }

        TEST_METHOD(should_create_valid_object_if_return_is_non_void_default_reducer)
        {
            
            namespace vfi = vf::implementation;

            // default_value_reducer non-void, non-reference return type
            typedef int (signature_t)();
            typedef vf::result<signature_t>::type result_t;
            typedef vfi::multicast_delegate_default_impl<signature_t>* exp_t;

            if (auto mc = vex::as_intrusive_from_out(
                vf::make_raw_multicast_delegate<signature_t>(vf::default_reducer(42)))) 
            {
                typedef decltype(mc) act_ip_t;
                typedef act_ip_t::element_type* act_t;

                Assert::IsTrue(std::is_same<exp_t, act_t>::value, wlog_message() 
                    << "expected: " << std::endl 
                    << typeid(exp_t).name() << std::endl
                    << "got: " << std::endl
                    << typeid(act_t).name() | as_string);
            } else {
                Assert::Fail(L"make_raw_multicast_delegate failed!");
            }
        }

        TEST_METHOD(should_create_valid_object_if_return_is_ref_const_default_reducer)
        {
            
            namespace vfi = vf::implementation;

            // default_value_reducer non-void, reference to const return type  
            typedef int const & (signature_t)();
            typedef vf::result<signature_t>::type result_t;
            typedef vfi::multicast_delegate_default_impl<signature_t>* exp_t;

            if (auto mc = vex::as_intrusive_from_out(
                vf::make_raw_multicast_delegate<signature_t>(vf::default_reducer(c_42)))) 
            {
                typedef decltype(mc) act_ip_t;
                typedef act_ip_t::element_type* act_t;

                Assert::IsTrue(std::is_same<exp_t, act_t>::value, wlog_message() 
                    << "expected: " << std::endl 
                    << typeid(exp_t).name() << std::endl
                    << "got: " << std::endl
                    << typeid(act_t).name() | as_string);
            } else {
                Assert::Fail(L"make_raw_multicast_delegate failed!");
            }
        }

        TEST_METHOD(should_create_valid_object_if_return_is_ref_default_reducer)
        {
            
            namespace vfi = vf::implementation;

            // default_value_reducer non-void, reference return type   
            typedef int & (signature_t)();
            typedef vf::result<signature_t>::type result_t;
            typedef vfi::multicast_delegate_default_impl<signature_t>* exp_t;

            if (auto mc = vex::as_intrusive_from_out(
                vf::make_raw_multicast_delegate<signature_t>(vf::default_reducer(s_42)))) 
            {
                typedef decltype(mc) act_ip_t;
                typedef act_ip_t::element_type* act_t;

                Assert::IsTrue(std::is_same<exp_t, act_t>::value, wlog_message() 
                    << "expected: " << std::endl 
                    << typeid(exp_t).name() << std::endl
                    << "got: " << std::endl
                    << typeid(act_t).name() | as_string);
            } else {
                Assert::Fail(L"make_raw_multicast_delegate failed!");
            }
        }

        TEST_METHOD(should_create_valid_object_if_return_is_non_void_custom_reducer)
        {
            
            namespace vfi = vf::implementation;

            typedef int (signature_t)();
            typedef std::vector<int> reducer_result_t;
            auto t_reducer = [](vf::contract::invoker_range<int>* p_range) 
                -> std::vector<int> 
            { 
                std::vector<int> t_result;
                // stupidly, intellisense does not recognize the convertibility
                // of invoker_range to a range contracts... the parameter of
                // as_any_from_in should be (and is) deducible...
                Assert::IsTrue(std::is_convertible<
                    decltype(p_range), 
                    vi::contract::range<vi::one_pass, int>*
                >::value);
                // using experimental adl syntax:
                if (auto t_range = vex::as_any_from_input() | p_range) {
                    while (t_range->move_front()) {
                        t_result.push_back(t_range->front());
                    }
                }
                return std::move(t_result); 
            };
            typedef vfi::multicast_delegate_default_impl<signature_t, decltype(t_reducer)>* exp_t;

            if (auto mc = vex::as_intrusive_from_out(
                vf::make_raw_multicast_delegate<signature_t>(t_reducer))) 
            {
                typedef decltype(mc) act_ip_t;
                typedef act_ip_t::element_type* act_t;

                Assert::IsTrue(std::is_same<exp_t, act_t>::value, wlog_message() 
                    << "expected: " << std::endl 
                    << typeid(exp_t).name() << std::endl
                    << "got: " << std::endl
                    << typeid(act_t).name() | as_string);

                auto t_result = mc->invoke();
                Assert::IsTrue(t_result.empty(), L"expected empty vector!");
            } else {
                Assert::Fail(L"make_raw_multicast_delegate failed!");
            }
        }

        template<class TRet>
        auto make_aggregating_reducer()
            -> std::function<std::vector<TRet> (vf::contract::invoker_range<TRet>*)>
        {
            return [](vf::contract::invoker_range<TRet>* p_range) -> std::vector<TRet> { 
                std::vector<TRet> t_result;
                if (p_range) {
                    while (p_range->move_front()) {
                        t_result.push_back(p_range->front());
                    }
                }
                return t_result; // std::move(t_result);
            };
        }

        template<class TSignature, class TRet>
        auto combine(
            vf::contract::multicast_delegate<TSignature, TRet>* p_left,
            vf::contract::delegate<TSignature>* p_right)
            -> vf::contract::multicast_delegate<TSignature, TRet>*
        {
            if (p_left) { 
                if (p_right) {
                    return p_left->combine(p_right);
                }
            }

            return nullptr;
        }

        template<class TSignature, class TRet, class TFunction>
        auto combine(
            vf::contract::multicast_delegate<TSignature, TRet>* p_left,
            TFunction&& p_right)
            -> vf::contract::multicast_delegate<TSignature, TRet>*
        {
            if (p_left) {
                if (auto t_right = vf::make_any_delegate<TSignature>(std::forward<TFunction>(p_right))) {
                    return p_left->combine(t_right.get());
                }
            }

            return nullptr;
        }

        TEST_METHOD(should_create_combine_and_release_memory_with_aggregating_reducer_arity_0)
        {
            auto mc = vex::as_intrusive_from_out(
                static_cast<vf::contract::multicast_delegate<int (), std::vector<int> >*>(
                vf::make_raw_multicast_delegate<int ()>(this->make_aggregating_reducer<int>())));
            if (mc) {
                auto t_result = mc->invoke();
                Assert::IsTrue(t_result.empty(), L"expected empty vector!");
            } else {
                Assert::Fail(L"make_raw_multicast_delegate failed!");
            }

            // add delegates:
            for (auto t_index = 0; t_index < 3; ++t_index) {
                if (!(mc = vex::as_intrusive_from_out(this->combine(mc.get(), [=]() { return t_index; })))) {
                    Assert::Fail(L"combine failed!");
                } else {
                    auto t_result = mc->invoke();
                    Assert::AreEqual(t_index + 1, (int)t_result.size(), wlog_message() <<
                        "expected vector with " << t_index << " elements!" | as_string);
                    for (int i = 0; i <= t_index; ++i) {
                        Assert::AreEqual(i, t_result[i]);
                    }
                }
            }
        }

        TEST_METHOD(should_create_combine_and_release_memory_with_aggregating_reducer_arity_1)
        {
            auto mc = vex::as_intrusive_from_out(
                static_cast<vf::contract::multicast_delegate<int (int), std::vector<int> >*>(
                vf::make_raw_multicast_delegate<int (int)>(this->make_aggregating_reducer<int>())));
            if (mc) {
                auto t_result = mc->invoke(42);
                Assert::IsTrue(t_result.empty(), L"expected empty vector!");
            } else {
                Assert::Fail(L"make_raw_multicast_delegate failed!");
            }

            // add delegates:
            for (auto t_index = 0; t_index < 3; ++t_index) {
                if (!(mc = vex::as_intrusive_from_out(this->combine(mc.get(), [=](int p) { return p + t_index; })))) {
                    Assert::Fail(L"combine failed!");
                } else {
                    auto t_result = mc->invoke(42);
                    Assert::AreEqual(t_index + 1, (int)t_result.size(), wlog_message() <<
                        "expected vector with " << t_index << " elements!" | as_string);
                    for (int i = 0; i <= t_index; ++i) {
                        Assert::AreEqual(42 + i, t_result[i]);
                    }
                }
            }
        }

        TEST_METHOD(should_create_combine_and_release_memory_with_aggregating_reducer_arity_2)
        {
            auto mc = vex::as_intrusive_from_out(
                static_cast<vf::contract::multicast_delegate<int (int, const std::string&), std::vector<int> >*>(
                vf::make_raw_multicast_delegate<int (int, const std::string&)>(this->make_aggregating_reducer<int>())));
            if (mc) {
                auto t_result = mc->invoke(42, "bimbo");
                Assert::IsTrue(t_result.empty(), L"expected empty vector!");
            } else {
                Assert::Fail(L"make_raw_multicast_delegate failed!");
            }

            // add delegates:
            for (auto t_index = 0; t_index < 3; ++t_index) {
                if (!(mc = vex::as_intrusive_from_out(this->combine(
                    mc.get(), 
                    [=](int p, const std::string & p_str) { return p + t_index + p_str.length(); })))) 
                {
                    Assert::Fail(L"combine failed!");
                } else {
                    std::stringstream t_str; 
                    t_str << t_index;
                    auto t_result = mc->invoke(42, t_str.str());
                    Assert::AreEqual(t_index + 1, (int)t_result.size(), wlog_message() <<
                        "expected vector with " << t_index << " elements!" | as_string);
                    for (int i = 0; i <= t_index; ++i) {
                        Assert::AreEqual((int)(42 + i + t_str.str().length()), t_result[i]);
                    }
                }
            }
        }

        TEST_METHOD(should_keep_lvalueness_of_arg_1_with_default_reducer)
        {
            auto mc = vex::as_intrusive_from_out(
                static_cast<vf::contract::multicast_delegate<void (int&)>*>(
                vf::make_raw_multicast_delegate<void (int&)>()));

            int t_ref_arg = 0;

            if (mc) {
                mc->invoke(t_ref_arg);
            } else {
                Assert::Fail(L"make_raw_multicast_delegate failed!");
            }

            // add delegates:
            for (auto t_index = 0; t_index < 3; ++t_index) {
                if (!(mc = vex::as_intrusive_from_out(this->combine(mc.get(), [&](int& p_ref) { 
                    p_ref += 1;
                    Assert::AreEqual(std::addressof(p_ref), std::addressof(t_ref_arg));
                })))) 
                {
                    Assert::Fail(L"combine failed!");
                } 
                else 
                {
                    // t_index = 0, 1 delegate, t_ref_arg = 1
                    // t_index = 1, 2 delegates, t_ref_arg = t_ref_arg + 1 + 1 = 1 + 1 + 1 = 3
                    // t_index = 2, 3 delegates, t_ref_arg = t_ref_arg + 1 + 1 + 1 = 3 + 1 + 1 + 1 = 6
                    mc->invoke(t_ref_arg);
                }
            }

            Assert::AreEqual(6, t_ref_arg);
        }

        class test_non_copyable : boost::noncopyable {
            int m_value;
        public:
            test_non_copyable() : m_value(0) {}
            void set(int p_value) { m_value = p_value; }
            int get() const { return m_value; }
        };

        TEST_METHOD(should_keep_lvalueness_of_arg_1_and_arg2_with_default_reducer)
        {
            auto mc = vex::as_intrusive_from_out(
                static_cast<vf::contract::multicast_delegate<void (int const &, test_non_copyable&)>*>(
                vf::make_raw_multicast_delegate<void (int const &, test_non_copyable&)>()));

            const int t_ref_arg1 = 0;
            test_non_copyable t_ref_arg2;

            if (mc) {
                mc->invoke(t_ref_arg1, t_ref_arg2);
            } else {
                Assert::Fail(L"make_raw_multicast_delegate failed!");
            }

            // add delegates:
            for (auto t_index = 0; t_index < 3; ++t_index) {
                if (!(mc = vex::as_intrusive_from_out(this->combine(mc.get(),
                    [=, &t_ref_arg1, &t_ref_arg2](int const & p_arg1, test_non_copyable & p_arg2) { 
                        Assert::AreEqual(std::addressof(t_ref_arg1), std::addressof(p_arg1));
                        Assert::AreEqual(
                            static_cast<void*>(std::addressof(t_ref_arg2)), 
                            static_cast<void*>(std::addressof(p_arg2)));
                        p_arg2.set(t_index);
                })))) 
                {
                    Assert::Fail(L"combine failed!");
                } 
                else 
                {
                    mc->invoke(t_ref_arg1, t_ref_arg2);
                    Assert::AreEqual(t_index, t_ref_arg2.get());
                }
            }
        }
    };

    int test_class::s_42 = 42;
}}